summaryrefslogblamecommitdiffstatshomepage
path: root/frontend/src/routes/comics/[id]/+page.svelte
blob: 28a8dde3c3d6f81e4fad3a6a7c44ac0dec12efc3 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12



                                                         







                                                                           
                                                               












                                                                               
                                                       
                                                                          

                                                                               





                                                                              

                                                  
 
                                           

                                           
                                             
                                                                                    
 





                                                                             

         

                                                                                                        

         


                                                                                                              

         

                                                                                                     



                                      

                                                                                                      
                  
                                              



                                                                              




















                                                                                            

         
                                             
 
                    


                                 



                                                                       



                          


                                                                          
                                      
                                                            


                                                                                
                                                                                                          
                                                                            


                                                                                                                                             
                                                      
                                                                               
                                                                                

                                                                                          



                                                              

                                                                                              
                                      
                                                                  





                                                               
                                                                                        



                       
                                                                              








                                                                            



                          
<script lang="ts">
	import { beforeNavigate } from '$app/navigation';
	import { updateComics } from '$gql/Mutations';
	import { comicQuery } from '$gql/Queries';
	import { omitIdentifiers, type OmitIdentifiers } from '$gql/Utils';
	import {
		UpdateMode,
		type FullComicFragment,
		type PageFragment,
		type UpdateComicInput
	} from '$gql/graphql';
	import { comicPending } from '$lib/Form';
	import { initReaderContext } from '$lib/Reader.svelte';
	import { toastFinally } from '$lib/Toasts';
	import { preventOnPending } from '$lib/Utils';
	import BookmarkButton from '$lib/components/BookmarkButton.svelte';
	import Guard from '$lib/components/Guard.svelte';
	import Head from '$lib/components/Head.svelte';
	import OrganizedButton from '$lib/components/OrganizedButton.svelte';
	import RemovePageButton from '$lib/components/RemovePageButton.svelte';
	import SubmitButton from '$lib/components/SubmitButton.svelte';
	import Titlebar from '$lib/components/Titlebar.svelte';
	import Grid from '$lib/containers/Grid.svelte';
	import ComicForm from '$lib/forms/ComicForm.svelte';
	import Gallery from '$lib/gallery/Gallery.svelte';
	import PageView from '$lib/reader/PageView.svelte';
	import Reader from '$lib/reader/Reader.svelte';
	import ComicScrapeForm from '$lib/scraper/ComicScrapeForm.svelte';
	import { initScraperContext } from '$lib/scraper/Scraper.svelte';
	import { initSelectionContext } from '$lib/selection/Selection.svelte';
	import ComicDelete from '$lib/tabs/ComicDelete.svelte';
	import ComicDetails from '$lib/tabs/ComicDetails.svelte';
	import Tab from '$lib/tabs/Tab.svelte';
	import Tabs from '$lib/tabs/Tabs.svelte';
	import SelectionControls from '$lib/toolbar/SelectionControls.svelte';
	import { getContextClient } from '@urql/svelte';
	import { untrack } from 'svelte';
	import type { PageProps } from './$types';

	let { data }: PageProps = $props();
	const client = getContextClient();
	const reader = initReaderContext();
	const scraper = initScraperContext();
	const selection = initSelectionContext<PageFragment>('Page', (p) => p.path);

	let comic: FullComicFragment | undefined = $state();
	let input: OmitIdentifiers<FullComicFragment> | undefined = $state();
	let updateInput = $state(true);

	function invalidateInput() {
		updateInput = true;
	}

	function submit(input: UpdateComicInput) {
		updateComics(client, { ids: data.id, input }).then(invalidateInput).catch(toastFinally);
	}

	function toggle(field: keyof Pick<UpdateComicInput, 'bookmarked' | 'favourite' | 'organized'>) {
		if (!comic) return;
		updateComics(client, { ids: data.id, input: { [field]: !comic[field] } }).catch(toastFinally);
	}

	function updateCover(id: number) {
		updateComics(client, { ids: data.id, input: { cover: { id } } }).catch(toastFinally);
	}

	function removePages() {
		updateComics(client, {
			ids: data.id,
			input: { pages: { ids: selection.ids, options: { mode: UpdateMode.Remove } } }
		})
			.then(selection.clear)
			.catch(toastFinally);
	}

	beforeNavigate((navigation) => preventOnPending(navigation, pending));
	let result = $derived(comicQuery(client, { id: data.id }));
	let pending = $derived(comicPending(comic, input));

	$effect(() => {
		if (!$result.stale) {
			untrack(() => {
				if ($result.data?.comic.__typename === 'FullComic') {
					comic = $result.data.comic;

					if (updateInput) {
						input = omitIdentifiers($result.data.comic);
						updateInput = false;
					}

					reader.pages = comic.pages;
					selection.view = comic.pages;
					scraper.reset();
				}
			});
		}
	});
</script>

<Head section="Comic" title={comic?.title} />

{#if comic && input}
	<Grid>
		<header>
			<Titlebar
				title={comic.title}
				subtitle={comic.originalTitle}
				favourite={comic.favourite}
				onfavourite={() => toggle('favourite')}
			/>
		</header>

		<aside>
			<Tabs badges={{ edit: pending }}>
				<Tab initial id="details" title="Details">
					<ComicDetails {comic} />
				</Tab>
				<Tab id="edit" title="Edit">
					<div class="flex flex-col gap-4">
						<div class="flex gap-2 text-sm">
							<SelectionControls page>
								<RemovePageButton onclick={removePages} />
							</SelectionControls>
							<div class="grow"></div>
							<BookmarkButton bookmarked={comic.bookmarked} onclick={() => toggle('bookmarked')} />
							<OrganizedButton organized={comic.organized} onclick={() => toggle('organized')} />
						</div>
						<ComicForm bind:input {submit}>
							<div class="flex gap-2">
								<div class="grow"></div>
								<SubmitButton {pending} />
							</div>
						</ComicForm>
					</div>
				</Tab>
				<Tab id="scrape" title="Scrape">
					<ComicScrapeForm {comic} onupsert={invalidateInput} />
				</Tab>
				<Tab id="deletion" title="Delete">
					<ComicDelete {comic} />
				</Tab>
			</Tabs>
		</aside>

		<main class="overflow-auto">
			<Gallery pages={comic.pages} open={reader.open} {updateCover} />
		</main>
	</Grid>

	<Reader>
		<PageView layout={input.layout} direction={input.direction} />
		{#snippet sidebar()}
			{#if input}
				<ComicForm bind:input {submit}>
					<div class="flex justify-end gap-2">
						<SubmitButton {pending} />
					</div>
				</ComicForm>
			{/if}
		{/snippet}
	</Reader>
{:else}
	<Guard {result} />
{/if}